/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.spring.cloud;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.pajmas.daydream.building.AbstractEzBuilder;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * @author william
 * @since 2024/3/25
 */
public class RouteDefinitionBuilder extends AbstractEzBuilder<RouteDefinition> {
    private final Map<String, Object> metadata;
    private final List<PredicateDefinition> predicateDefinitions;
    private final List<FilterDefinition> filterDefinitions;
    private String id;
    private URI uri;
    private Integer order;

    private RouteDefinitionBuilder() {
        this.metadata = Maps.newHashMap();
        this.predicateDefinitions = Lists.newArrayList();
        this.filterDefinitions = Lists.newArrayList();
    }

    public static RouteDefinitionBuilder newBuilder() {
        return new RouteDefinitionBuilder();
    }

    public RouteDefinitionBuilder id(String id) {
        this.id = id;
        return this;
    }

    public RouteDefinitionBuilder uri(String uriTxt) {
        this.uri = URI.create(uriTxt);
        return this;
    }

    public RouteDefinitionBuilder uri(URI uri) {
        this.uri = uri;
        return this;
    }

    public RouteDefinitionBuilder uri(Consumer<URIBuilder> uriBuilderConsumer) throws URISyntaxException {
        URIBuilder uriBuilder = new URIBuilder();
        uriBuilderConsumer.accept(uriBuilder);
        this.uri = uriBuilder.build();
        return this;
    }

    public RouteDefinitionBuilder lbUri(String serviceName) throws URISyntaxException {
        this.uri = new URIBuilder()
                .setScheme("lb")
                .setPort(-1)
                .setHost(serviceName)
                .setPath(StringUtils.EMPTY)
                .build();
        return this;
    }

    public RouteDefinitionBuilder order(Integer order) {
        this.order = order;
        return this;
    }

    public RouteDefinitionBuilder addPredicate(PredicateDefinition definition) {
        this.predicateDefinitions.add(definition);
        return this;
    }

    public RouteDefinitionBuilder addPredicate(String expression) {
        this.predicateDefinitions.add(new PredicateDefinition(expression));
        return this;
    }

    public RouteDefinitionBuilder addPredicate(String expressionPrefix, String expressionSuffix) {
        String expression = StringUtils.join(expressionPrefix, "=", expressionSuffix);
        return this.addPredicate(expression);
    }

    public RouteDefinitionBuilder addPredicate(Class<? extends RoutePredicateFactory<?>> factoryClass,
                                               String... args) {
        String className = factoryClass.getSimpleName();
        int endIdx = StringUtils.indexOf(className, "RoutePredicateFactory");
        String expressionPrefix = StringUtils.substring(className, 0, endIdx);
        String expressionSuffix = StringUtils.join(args, ",");
        return this.addPredicate(expressionPrefix, expressionSuffix);
    }

    public RouteDefinitionBuilder addFilter(FilterDefinition definition) {
        this.filterDefinitions.add(definition);
        return this;
    }

    public RouteDefinitionBuilder addFilter(String expression) {
        this.filterDefinitions.add(new FilterDefinition(expression));
        return this;
    }

    public RouteDefinitionBuilder addFilter(String expressionPrefix, String expressionSuffix) {
        String expression = StringUtils.join(expressionPrefix, "=", expressionSuffix);
        this.filterDefinitions.add(new FilterDefinition(expression));
        return this;
    }

    public RouteDefinitionBuilder addFilter(Class<? extends GatewayFilterFactory<?>> factoryClass,
                                            String... args) {
        String className = factoryClass.getSimpleName();
        int endIdx = StringUtils.indexOf(className, "GatewayFilterFactory");
        String expressionPrefix = StringUtils.substring(className, 0, endIdx);
        String expressionSuffix = StringUtils.join(args, ",");
        return this.addFilter(expressionPrefix, expressionSuffix);
    }

    public RouteDefinitionBuilder addMetaData(String key, String value) {
        this.metadata.put(key, value);
        return this;
    }

    @Override
    protected RouteDefinition internalBuild() {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(this.id);
        definition.setUri(this.uri);
        definition.setOrder(ObjectUtils.firstNonNull(this.order, 0));
        if (MapUtils.isNotEmpty(this.metadata)) {
            definition.setMetadata(this.metadata);
        }
        if (CollectionUtils.isNotEmpty(this.predicateDefinitions)) {
            definition.setPredicates(this.predicateDefinitions);
        }
        if (CollectionUtils.isNotEmpty(this.filterDefinitions)) {
            definition.setFilters(this.filterDefinitions);
        }
        return definition;
    }

    public interface Customizer extends AbstractEzBuilder.Customizer<RouteDefinition, RouteDefinitionBuilder> {
    }
}
